﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Windows.Input;

namespace Foundation
{
    /// <summary>
    /// View Model.
    /// </summary>
    [DataContract]
    public class ViewModel : ViewModelBase
    {
        private Dictionary<ICommand, CommandBinding> _commandBindings;
        private Dictionary<string, PropertyBinding> _propertyBindings;

        /// <summary>
        /// Gets the <see cref="System.Windows.Input.CommandBinding"/> with the specified command.
        /// </summary>
        public CommandBinding this[ICommand command]
        {
            get
            {
                if (!_commandBindings.ContainsKey(command))
                {
                    var commandBinding = new CommandBinding(command);
                    _commandBindings.Add(command, commandBinding);
                    CommandBindings.Add(commandBinding);
                }

                return _commandBindings[command];
            }
        }

        public PropertyBinding this[Expression<Func<object>> lambdaExpression]
        {
            get
            {
                var propertyName = GetPropertyName(lambdaExpression);
                if (!_propertyBindings.ContainsKey(propertyName))
                {
                    var propertyBinding = new PropertyBinding(propertyName);
                    _propertyBindings.Add(propertyName, propertyBinding);
                }

                return _propertyBindings[propertyName];
            }
        }

        public static Action ApplicationClosing = () => { };

        public CancelEventHandler OnClosing = (o, e) => { };

        /// <summary>
        /// Initializes a new instance of the <see cref="ViewModel"/> class.
        /// </summary>
        public ViewModel()
        {
            Initialize();
        }

        /// <summary>
        /// Gets the command bindings.
        /// </summary>
        public CommandBindingCollection CommandBindings { get; private set; }

        /// <summary>
        /// Gets the specified expression.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <param name="defaultValue">The default value.</param>
        /// <returns></returns>
        public TProperty Get<TProperty>(Expression<Func<TProperty>> expression, TProperty defaultValue = default(TProperty))
        {
            var propertyName = GetPropertyName(expression);
            if (!Values.ContainsKey(propertyName))
                Values.Add(propertyName, defaultValue);
            return (TProperty) Values[propertyName];
        }

        /// <summary>
        /// Sets the specified expression.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="expression">The expression.</param>
        /// <param name="value">The value.</param>
        public void Set<TProperty>(Expression<Func<TProperty>> expression, TProperty value)
        {
            var propertyName = GetPropertyName(expression);
            RaisePropertyChangingEvent(propertyName);
            if (!Values.ContainsKey(propertyName))
                Values.Add(propertyName, value);
            else Values[propertyName] = value;
            RaisePropertyChangedEvent(propertyName);
        }

        /// <summary>
        /// Raises the property changing event.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="lambdaExpression">The lambda expression.</param>
        public void RaisePropertyChangingEvent<TProperty>(Expression<Func<TProperty>> lambdaExpression)
        {
            var propertyName = GetPropertyName(lambdaExpression);
            RaisePropertyChangingEvent(propertyName);
        }

        /// <summary>
        /// Raises the property changed event.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="lambdaExpression">The lambda expression.</param>
        public void RaisePropertyChangedEvent<TProperty>(Expression<Func<TProperty>> lambdaExpression)
        {
            var propertyName = GetPropertyName(lambdaExpression);
            RaisePropertyChangedEvent(propertyName);
        }

        [OnDeserializing]
        private void Initialize(StreamingContext context = default(StreamingContext))
        {
            _commandBindings = new Dictionary<ICommand, CommandBinding>();
            _propertyBindings = new Dictionary<string, PropertyBinding>();
            CommandBindings = new CommandBindingCollection();
            PropertyChanging += OnPropertyChanging;
            PropertyChanged += OnPropertyChanged;
        }

        private void OnPropertyChanging(object sender, PropertyChangingEventArgs e)
        {
            var propertyName = e.PropertyName;
            if (!_propertyBindings.ContainsKey(propertyName)) return;
            var binding = _propertyBindings[propertyName];
            if (binding != null) binding.InvokePropertyChanging(sender, e);
        }

        private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var propertyName = e.PropertyName;
            if (!_propertyBindings.ContainsKey(propertyName)) return;
            var binding = _propertyBindings[propertyName];
            if (binding != null) binding.InvokePropertyChanged(sender, e);
        }

        public class PropertyBinding
        {
            public PropertyBinding(string propertyName)
            {
                PropertyName = propertyName;
            }

            public event PropertyChangingEventHandler PropertyChanging = (sender, args) => { };
            public event PropertyChangedEventHandler PropertyChanged = (sender, args) => { };
            public string PropertyName { get; private set; }

            public void InvokePropertyChanging(object sender, PropertyChangingEventArgs args)
            {
                PropertyChanging(sender, new PropertyChangingEventArgs(PropertyName));
            }

            public void InvokePropertyChanged(object sender, PropertyChangedEventArgs args)
            {
                PropertyChanged(sender, new PropertyChangedEventArgs(PropertyName));
            }
        }
    }
}